import * as qs from 'qs-esm'
import { useGlobalStore } from '@/stores/global'

// 请求地址
const BASE_URL = 'http://localhost:8080'

// 基础返回数据类型
interface IBaseResData<T> {
  code: number
  msg: string
  message: string
  data: T
}

// 请求参数类型
interface IRequestOptions extends UniNamespace.RequestOptions {
  params?: Record<string, any> // 扩展 params
  header?: {
    // 扩展 content-type
    'Content-Type': 'application/json' | 'application/x-www-form-urlencoded' | string
    Authorization?: string
    [key: string]: any
  }
  // 扩展 loading
  isLoading?: boolean
  loadingText?: string
}

/**
 * 拦截器
 */
const httpInterceptorOptions: UniNamespace.InterceptorOptions = {
  // 拦截前触发
  invoke(options: IRequestOptions) {
    // 1. 非 http/s 开头需拼接地址
    if (!options.url.startsWith('http') || options.url.startsWith('https')) {
      // #ifdef H5
      try {
        // 检测 URL 是否正确
        new URL(options.url, BASE_URL)
      } catch (e) {
        throw new Error('request 请求地址错误！')
      }
      // #endif
      options.url = BASE_URL + options.url
    }
    // 2. 请求超时
    options.timeout = 10000 // 10s
    // 3. 添加 token 请求头标识
    const globalStore = useGlobalStore()
    const token = globalStore.token
    if (token) {
      options.header!.Authorization = `Bearer ${token}`
    }
  }
}
uni.addInterceptor('request', httpInterceptorOptions) // 拦截 request 请求
uni.addInterceptor('uploadFile', httpInterceptorOptions) // 拦截 uploadFile 文件上传

/**
 * request 网络请求封装
 */
const request = async <T>(requestOptions: IRequestOptions) => {
  let {
    url,
    method = 'GET',
    params,
    data,
    header,
    timeout = 10000,
    isLoading = false,
    loadingText = '加载中...'
  } = requestOptions

  // 显示加载中
  if (isLoading) {
    await uni.showLoading({
      title: loadingText
    })
  }

  // 处理参数
  if (method === 'GET') {
    // 处理 data 参数
    data = params || data
  } else {
    // 处理 params 参数
    const query = params && qs.stringify(params)
    if (query) {
      url += '?' + query
    }
  }

  // 返回 Promise
  return new Promise<IBaseResData<T>>((resolve, reject) => {
    uni.request({
      url,
      method,
      data,
      timeout,
      header: {
        'Content-Type': 'application/json',
        ...header
      },
      // #ifdef H5
      withCredentials: true, // 跨域请求时是否携带凭证（cookies）
      // #endif
      success: async (res) => {
        // 状态码错误
        if (!(res.statusCode >= 200 && res.statusCode < 300)) {
          return reject(res)
        }

        const responseData = res.data as IBaseResData<T>
        const { code, msg, message } = responseData
        // 过滤并获取提示信息
        const title = escapeHtml(msg || message)

        // 请求成功处理
        if (code >= 200 && code < 300) {
          return resolve(responseData)
        }

        // 请求失败处理
        switch (code) {
          case 401: // token 过期
            await showToast('登录已过期，请重新登录！')
            // 清理 token
            const globalStore = useGlobalStore()
            globalStore.clearToken()
            // 跳转登录页
            await uni.redirectTo({
              url: '/pages/login/login'
            })
            break
          case 500: // 服务器错误
            await showToast('服务器错误，请联系管理员！')
            reject(res)
            break
          default:
            // 其它错误
            await showToast(title)
            reject(res)
        }
      },
      fail: async (err) => {
        await showToast('网络错误，换个网络试试')
        reject(err)
      },
      complete: () => {
        isLoading && uni.hideLoading()
      }
    })
  })
}

/**
 * HTML 转义函数，防止 XSS 攻击
 * @param unsafeStr 需要转义的字符串
 */
function escapeHtml(unsafeStr: string) {
  return unsafeStr
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;')
}

/**
 * 显示提示信息
 * @param title 提示的内容
 * @param icon 图标
 */
async function showToast(
  title: string,
  icon: 'success' | 'loading' | 'error' | 'none' | 'fail' | 'exception' = 'none'
) {
  await uni.showToast({
    title,
    icon
  })
}

export default request
